#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <mutex>
#include <cassert>
#include <algorithm>
#include <jsoncpp/json/json.h>

#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "oj_view.hpp"
#include "../comm/httplib.h"
// #include "oj_model.hpp"
#include "oj_model2.hpp"

namespace ns_control
{
    using namespace std;
    using namespace ns_model;
    using namespace ns_util;
    using namespace ns_log;
    using namespace ns_view;
    using namespace httplib;

    class Machine
    {
    public:
        string ip;
        int port;
        int load;
        mutex *mtx; // 多线程访问load

    public:
        Machine() : ip(""), port(0), load(0), mtx(nullptr)
        {
        }
        ~Machine()
        {
        }
        void IncLoad()
        {
            if (mtx)
                mtx->lock();
            ++load;
            if (mtx)
                mtx->unlock();
        }
        void ReLoad()
        {
            if (mtx)
                mtx->lock();
            load = 0;
            if (mtx)
                mtx->unlock();
        }
        void DecLoad()
        {
            if (mtx)
                mtx->lock();
            --load;
            if (mtx)
                mtx->unlock();
        }
    };

    const string machine_conf_path = "./conf/machines.conf";

    class LoadBalance
    {
    private:
        vector<Machine> _machines; // 提供编译服务的主机
        vector<int> _online;       // 在线的编译主机
        vector<int> _offline;      // 离线的编译主机
        mutex *mtx;                // 多线程访问vector数据
    public:
        LoadBalance()
        {
            assert(LoadMachineConf(machine_conf_path));
            LOG(INFO) << "配置文件" << machine_conf_path << "已加载成功" << endl;
            mtx = new mutex(); // 何时释放？？
        }
        ~LoadBalance()
        {
            delete mtx;
            for (auto &m : _machines)
            {
                delete m.mtx;
            }
        }

        bool LoadMachineConf(const string &conf_path)
        {
            ifstream in(conf_path);
            if (!in.is_open())
            {
                LOG(FATAL) << "主机配置文件打开失败！" << endl;
                return false;
            }
            string line;
            while (getline(in, line))
            {
                vector<string> tokens;
                StringUtil::SplitString(line, &tokens, ":");
                if (tokens.size() != 2)
                {
                    LOG(ERROR) << "主机配置文件内容有误：" << line << endl;
                    continue;
                }
                Machine m;
                m.ip = tokens[0];
                m.port = stoi(tokens[1]);
                m.load = 0;
                m.mtx = new mutex(); // 何时释放？？
                _online.emplace_back(_machines.size());
                _machines.emplace_back(m);
                LOG(INFO) << "主机列表已添加一台主机，id: " << _online.back() << " ip: " << m.ip << " port: " << m.port << endl;
            }
            in.close();
            return true;
        }
        // id: 输出型，主机的id
        // m： 输出型，指向主机的指针
        bool SmartChoice(int *id, Machine **m)
        {
            // 轮询+hash
            mtx->lock();
            int load_num = _online.size();
            if (load_num == 0)
            {
                mtx->unlock();
                LOG(FATAL) << "所有编译服务已全部离线，请尽快排查！" << endl;
                return false;
            }

            // 遍历找到当前负载最小的主机
            uint64_t min_load = _machines[_online[0]].load;
            *id = _online[0];
            *m = &_machines[_online[0]];
            for (int i = 1; i < load_num; i++)
            {
                uint64_t temp_load = _machines[_online[i]].load;
                if (min_load > temp_load)
                {
                    min_load = temp_load;
                    *id = _online[i];
                    *m = &_machines[_online[i]];
                }
            }
            mtx->unlock();
            return true;
        }
        void Offline(int id)
        {
            mtx->lock();
            for (auto iter = _online.begin(); iter != _online.end(); iter++)
            {
                if (*iter == id)
                {
                    _online.erase(iter);
                    _offline.emplace_back(id);
                    break;
                }
            }
            mtx->unlock();
        }
        void Online()
        {
            mtx->lock();
            _online.insert(_online.end(), _offline.begin(), _offline.end());
            _offline.erase(_offline.begin(), _offline.end());
            mtx->unlock();

            LOG(INFO) << "所有主机重新上线！" << endl;
        }
        void ShowMachines()
        {
            LOG(INFO) << "当前在线主机：";
            for (const auto &id : _online)
            {
                cout << id << " ";
            }
            cout << endl;
            LOG(INFO) << "当前离线主机：";
            for (const auto &id : _offline)
            {
                cout << id << " ";
            }
            cout << endl;
        }
    };

    class Control
    {
    private:
        Model _model;              // 提供后台数据
        View _view;                // 渲染网页
        LoadBalance _load_balance; // 负载均衡器

    public:
        Control()
        {
        }
        ~Control()
        {
        }
        // 重新上线全部主机
        void RecoverMachine()
        {
            _load_balance.Online();
        }
        bool AllQuestions(string *html)
        {
            bool ret = true;
            vector<struct Question> all;
            if (_model.GetAllQuestions(&all))
            {
                // 按照题号升序排序
                sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2)
                     { return stoi(q1.number) < stoi(q2.number); });

                // 获取全部题目成功,将题目列表构建成网页
                _view.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题库失败";
                ret = false;
            }
            return ret;
        }
        bool OneQuestion(const string &number, string *html)
        {
            bool ret = true;
            struct Question q;
            if (_model.GetOneQuestion(number, &q))
            {
                // 获取指定题目成功，将题目数据构建成网页
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html = "获取指定题目失败，题号：" + number;
                ret = false;
            }
            return ret;
        }

        void Judge(const string &number, const string &in_json, string *out_json)
        {
            // 反序列化in_json
            Json::Value in_value;
            Json::Reader read;
            read.parse(in_json, in_value);
            string code = in_value["code"].asString();
            string input = in_value["input"].asString();

            // 根据题号，拼接用户代码  + 测试用例, 序列化
            Question q;
            _model.GetOneQuestion(number, &q);
            Json::Value compile_value;
            compile_value["code"] = code + "\n" + q.tail;
            compile_value["input"] = input;
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            Json::FastWriter writer;
            string compile_string = writer.write(compile_value);

            // 向编译服务负载均衡式的发起请求
            // 一直请求，直到成功响应
            while (true)
            {
                int id = 0;

                Machine *m = nullptr;
                if (!_load_balance.SmartChoice(&id, &m))
                {
                    break; // 编译服务全部挂掉了！
                }

                Client cli(m->ip, m->port);
                m->IncLoad();
                if (auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))
                {
                    LOG(INFO) << "请求当前主机 id: " << id << " 详情 " << m->ip << ":" << m->port << " 主机负载：" << m->load << endl;
                    if (res->status == 200) // 编译服务正确响应
                    {
                        // 拿到编译结果通过out_json返回给上层
                        *out_json = res->body;
                        m->DecLoad();
                        LOG(INFO) << "编译服务请求成功，主机详情：" << m->ip << ":" << m->port << " 主机负载：" << m->load << endl;
                        break;
                    }

                    m->DecLoad();
                }
                else
                {
                    LOG(ERROR) << "当前主机可能已经离线 id: " << id << " 详情 " << m->ip << ":" << m->port << endl;
                    m->ReLoad();
                    _load_balance.Offline(id);
                    _load_balance.ShowMachines(); // 调试
                }
            }
        }

        void PageSkip(const string &path, string *out)
        {
            FileUtil::ReadFile(path, out, true);
        }
    };
} // namespace ns_control
